#include <cstdio>
#include <cmath>
#include <QtGui/QMouseEvent>
#include "defs.h"
#include "Go2DWidget.h"
#include <SOIL/SOIL.h>
#include <iostream>

#define GL_LINE_THICKNESS 1.0

GLfloat LINE_THICKNESS          = GL_LINE_THICKNESS;
GLfloat STAR_DIAMETER           = GL_LINE_THICKNESS * 4.0f;
GLfloat STONE_DIAMETER          = GL_LINE_THICKNESS * 23.0f;//22.5f;
GLfloat LINE_SPACING_LENGTHWISE = GL_LINE_THICKNESS * 22.0f;//23.7f;
GLfloat LINE_SPACING_WIDTHWISE  = GL_LINE_THICKNESS * 22.0f;
GLfloat BOARD_LENGTH            = GL_LINE_THICKNESS * 454.5f;
GLfloat BOARD_WIDTH             = GL_LINE_THICKNESS * 424.2f;
GLfloat BOARD_THICKNESS         = GL_LINE_THICKNESS * 151.5f;

void CheckGLError() 
{	
    GLuint err = glGetError();	
    if (err != GL_NO_ERROR)		
        std::cerr << "GL Error: "<< std::hex << err << std::endl;
}

Go2DWidget::Go2DWidget(QWidget *parent)
{
    setMouseTracking(true);
    xPos_cursor = yPos_cursor = -1;
    board = new AbstractBoard;
    turn = BLACK;
}

void Go2DWidget::loadTextures()
{
    
    tex_black = SOIL_load_OGL_texture(
            "images/black_64.png",
            SOIL_LOAD_RGBA,
            SOIL_CREATE_NEW_ID,
            SOIL_FLAG_INVERT_Y
    );

    tex_white = SOIL_load_OGL_texture(
            "images/white_64_temp.png",
            SOIL_LOAD_RGBA,
            SOIL_CREATE_NEW_ID,
            SOIL_FLAG_INVERT_Y
    );

    if (tex_black == 0 || tex_white == 0)
    {
        printf("SOIL loading error: '%s\n", SOIL_last_result());
    }

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}

void Go2DWidget::initializeGL()
{
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glFrontFace(GL_CCW);

    loadTextures();

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}

void Go2DWidget::resizeGL(int w, int h)
{
    GLfloat aspectRatio;
    int boardWidth = 19*LINE_THICKNESS + 20*LINE_SPACING_WIDTHWISE;
    int boardHeigth = 19*LINE_THICKNESS + 20*LINE_SPACING_LENGTHWISE;

    // Prevent a divide by zero
    if (h == 0)
        h = 1;

    // Set Viewport to window dimensions
    glViewport(0, 0, w, h);

    // Reset coordinate system
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    // Estabilish clipping volume
    aspectRatio = (GLfloat)w/ (GLfloat)h;
    if (w >= h)
        glOrtho(-100, (boardWidth + 1 - 100)*aspectRatio, -1, boardHeigth + 1, -1, 1);
    else
        glOrtho(-100, boardWidth + 1, -1/aspectRatio, (boardHeigth + 1)/aspectRatio, -100, 100);
//    glOrtho(0, w, h, 0, 0, 1);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    width = w;
    height = h;
}

void Go2DWidget::paintGL()
{
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glDisable(GL_TEXTURE_2D);
    drawBoard();
    CheckGLError();
    //drawPlayZones();
    CheckGLError();
    glEnable(GL_TEXTURE_2D);

    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    drawPlayedStones();
    CheckGLError();
    drawGhostStone();
    CheckGLError();

    drawOverlay();

    glFlush();

    swapBuffers();
}

void Go2DWidget::drawBoard()
{
    // Draw board area
    glColor3f((GLfloat)200.0/(GLfloat)255.0,
              (GLfloat)160.0/(GLfloat)255.0,
              (GLfloat)100.0/(GLfloat)255.0);

    glBegin(GL_QUADS);
        glVertex2f(0, 0);
        glVertex2f(19*LINE_THICKNESS + 20*LINE_SPACING_WIDTHWISE, 0);
        glVertex2f(19*LINE_THICKNESS + 20*LINE_SPACING_WIDTHWISE,
                    19*LINE_THICKNESS + 20*LINE_SPACING_LENGTHWISE);
        glVertex2f(0, 19*LINE_THICKNESS + 20*LINE_SPACING_LENGTHWISE);
    glEnd();

    // Draw horizontal lines
    glColor3f(0.0f, 0.0f, 0.0f);
    for (int i = 0; i < 19; i++)
    {
        glBegin(GL_LINES);
            glVertex2f(LINE_SPACING_WIDTHWISE,
                        LINE_SPACING_LENGTHWISE + i*(LINE_THICKNESS + LINE_SPACING_LENGTHWISE));
            glVertex2f(18*LINE_THICKNESS + 19*LINE_SPACING_WIDTHWISE,
                        LINE_SPACING_LENGTHWISE + i*(LINE_THICKNESS + LINE_SPACING_LENGTHWISE));
        glEnd();
    }

    // Draw vertical lines
    for (int i = 0; i < 19; i++)
    {
        glBegin(GL_LINES);
            glVertex2f(LINE_SPACING_WIDTHWISE + i*(LINE_THICKNESS + LINE_SPACING_WIDTHWISE),
                        LINE_SPACING_LENGTHWISE);
            glVertex2f(LINE_SPACING_WIDTHWISE + i*(LINE_THICKNESS + LINE_SPACING_WIDTHWISE),
                        18*LINE_THICKNESS + 19*LINE_SPACING_LENGTHWISE);
        glEnd();
    }
    
    drawHoshi();
}

void Go2DWidget::drawPlayedStones()
{
    for (int i = 0; i < 19; i++)
    {
        for (int j = 0; j < 19; j++)
        {
            int play = board->getPlay(j, i);
            if (play == EMPTY)
                continue;

            if (play == BLACK)
                //glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
                glBindTexture(GL_TEXTURE_2D, tex_black);
            else if (play == WHITE)
                //glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
                glBindTexture(GL_TEXTURE_2D, tex_white);
           drawStone(j, i, false);
        }
    } 
}

void Go2DWidget::drawGhostStone()
{
    int *coords = getBoardCoord(xPos_cursor, yPos_cursor);
   
    glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
    if (turn == BLACK)   
        //glColor4f(0.0f, 0.0f, 0.0f, 0.4f);
        glBindTexture(GL_TEXTURE_2D, tex_black);
    else if (turn == WHITE)
        //glColor4f(1.0f, 1.0f, 1.0f, 0.4f);
        glBindTexture(GL_TEXTURE_2D, tex_white);


    if (board->isEmptySpot(coords[0], coords[1]))
        drawStone(coords[0], coords[1], true);
}

void Go2DWidget::drawStone(int x_coord, int y_coord, bool ghost)
{

    if (x_coord <= -1 || x_coord >= 19 ||
        y_coord <= -1 || y_coord >= 19)
        return;
    
    glPushMatrix();
    if (!ghost)
    {
        glDisable(GL_TEXTURE_2D);
        glTranslatef((x_coord + 1) * (LINE_SPACING_WIDTHWISE + LINE_THICKNESS)
                        - (LINE_THICKNESS/(GLfloat)2) + STONE_DIAMETER/12,
                    (20 * (LINE_SPACING_LENGTHWISE + LINE_THICKNESS)) - 
                     ((y_coord + 1) * (LINE_SPACING_LENGTHWISE + LINE_THICKNESS)- 
                        (LINE_THICKNESS/(GLfloat)2) + STONE_DIAMETER/8),
                    0.0f);
        glColor4f(0.0f, 0.0f, 0.0f, 0.4f);
        drawDisk(10*LINE_THICKNESS);

        glPopMatrix();
        glPushMatrix();
        glEnable(GL_TEXTURE_2D);
    }   

    glTranslatef((x_coord + 1) * (LINE_SPACING_WIDTHWISE + LINE_THICKNESS) 
                    - (LINE_THICKNESS/(GLfloat)2),
                 (20 * (LINE_SPACING_LENGTHWISE + LINE_THICKNESS)) - 
                 ((y_coord + 1) * (LINE_SPACING_LENGTHWISE + LINE_THICKNESS) - (LINE_THICKNESS/(GLfloat)2)), 
                 0.0f);
    //drawDisk(STONE_DIAMETER/(GLfloat)2);

    GLfloat r = STONE_DIAMETER/(GLfloat)2;

    if (ghost)
        glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
    else
        glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

    glBegin(GL_QUADS);
        glTexCoord2i(0, 0); glVertex2f(-r, -r);
        glTexCoord2i(1, 0); glVertex2f(r, -r);
        glTexCoord2i(1, 1); glVertex2f(r, r);
        glTexCoord2i(0, 1); glVertex2f(-r, r);
    glEnd();

    /*glBegin(GL_QUADS);
        glTexCoord2i(0, 0); glVertex2f(-r, -r);
        glTexCoord2i(0, 1); glVertex2f(-r, r);
        glTexCoord2i(1, 1); glVertex2f(r, r);
        glTexCoord2i(1, 0); glVertex2f(r, -r);
    glEnd();*/

    glPopMatrix();
}

void Go2DWidget::drawHoshi()
{
    GLfloat radius = STAR_DIAMETER / (GLfloat)2;

    int x_coord[9] = {3, 3, 3, 9, 9, 9, 15, 15, 15};
    int y_coord[9] = {3, 9, 15, 3, 9, 15, 3, 9, 15};

    for (int i = 0; i < 9; i++)
    {
        glPushMatrix();

        glTranslatef((x_coord[i] + 1) * (LINE_SPACING_WIDTHWISE + LINE_THICKNESS) 
                        - (LINE_THICKNESS/(GLfloat)2) - (LINE_THICKNESS/2.0),
                     (y_coord[i] + 1) * (LINE_SPACING_LENGTHWISE + LINE_THICKNESS) 
                        - (LINE_THICKNESS/(GLfloat)2) - (LINE_THICKNESS/2.0),
                     0);
        drawDisk(radius);

        glPopMatrix();
    }
}

int *Go2DWidget::getBoardCoord(GLdouble x, GLdouble y)
{
    int *coords = new int[2];
    
    GLdouble x_i = LINE_SPACING_WIDTHWISE / (GLfloat)2;
    GLdouble y_i = LINE_SPACING_LENGTHWISE / (GLfloat)2;

    if (x < x_i || x > (x_i + 19*LINE_SPACING_WIDTHWISE) ||
        y < y_i || y > (y_i + 19*LINE_SPACING_LENGTHWISE))
    {
        coords[0] = -1;
        coords[1] = -1;
    }

    coords[0] = floor((x - x_i) / (LINE_SPACING_WIDTHWISE + LINE_THICKNESS));
    coords[1] = floor((y - y_i) / (LINE_SPACING_LENGTHWISE + LINE_THICKNESS));

    //printf("coord: %d %d\n", coords[0], coords[1]);
    
    // flip y coord
    coords[1] = 18 - coords[1];

    return coords;
}

void Go2DWidget::drawDisk(GLdouble radius)
{
    glBegin(GL_POLYGON);
        for (int i = 0; i <= 360; i += 3)
            glVertex2f(radius*cos((i*3.1415)/180), radius*sin((i*3.1415)/180));
    glEnd();
}

void Go2DWidget::drawPlayZones()
{
    glColor4f(1.0f, 0.0f, 0.0f, 0.2f);

    GLfloat x_i = LINE_SPACING_WIDTHWISE/(GLfloat)2;
    GLfloat y_i = LINE_SPACING_LENGTHWISE/(GLfloat)2;

    for (int i = 0; i <= 19; i++)
    {
        glBegin(GL_LINES);
            glVertex2f(x_i, y_i + i*(LINE_THICKNESS + LINE_SPACING_LENGTHWISE));
            glVertex2f(19*LINE_THICKNESS + 39*(LINE_SPACING_WIDTHWISE/(GLfloat)2),
                        y_i + i*(LINE_THICKNESS + LINE_SPACING_LENGTHWISE)); 
        glEnd();
    }

    for (int i = 0; i <= 19; i++)
    {
        glBegin(GL_LINES);
            glVertex2f(x_i + i*(LINE_THICKNESS + LINE_SPACING_WIDTHWISE),
                        y_i);
            glVertex2f(x_i + i*(LINE_THICKNESS + LINE_SPACING_WIDTHWISE),
                        19*LINE_THICKNESS + 39*(LINE_SPACING_LENGTHWISE/(GLfloat)2));
        glEnd();
    }

    glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
}

GLdouble *Go2DWidget::untransformCoords(int event_x, int event_y)
{
    GLint viewport[4];
    glGetIntegerv(GL_VIEWPORT, viewport);

    GLdouble modelview[16];
    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);

    GLdouble projection[16];
    glGetDoublev(GL_PROJECTION_MATRIX, projection);

    GLdouble zCoord;
    GLdouble *coords = new GLdouble[2];
    gluUnProject(event_x, viewport[3] - event_y, 0,
            modelview, projection, viewport,
            &coords[0], &coords[1], &zCoord);

    return coords;
}

void Go2DWidget::mouseMoveEvent(QMouseEvent *event)
{
    //printf("%d %d\n", event->x(), event->y());

    GLdouble *coords = untransformCoords(event->x(), event->y());
    xPos_cursor = coords[0];
    yPos_cursor = coords[1];

    paintGL();
}

void Go2DWidget::mouseReleaseEvent(QMouseEvent *event)
{
    GLdouble *raw_coords = untransformCoords(event->x(), event->y());
    int *board_coords = getBoardCoord(raw_coords[0], raw_coords[1]);

    if (board->getPlay(board_coords[0], board_coords[1]) == 0)
    {
        if (turn == BLACK)
        {
            if (board->makePlay(board_coords[0], board_coords[1], turn))
                turn = WHITE;
        }
        else if (turn == WHITE)
        {
            if (board->makePlay(board_coords[0], board_coords[1], turn))
                turn = BLACK;
        }
        paintGL();
    }
}

void Go2DWidget::undo()
{
    turn = board->undo();
    paintGL();
}

void Go2DWidget::drawOverlay()
{
    glColor4f(0.0f, 0.5f, 1.0f, 0.5f);

    glDisable(GL_TEXTURE_2D);

    drawSquare(-80, BOARD_LENGTH-50, 50);

    glEnable(GL_TEXTURE_2D);
}

void Go2DWidget::drawSquare(double x, double y, double l)
{
    glBegin(GL_QUADS);
        glVertex2f(x, y);
        glVertex2f(x+l, y);
        glVertex2f(x+l, y+l);
        glVertex2f(x, y+l);
    glEnd();
}
